/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/interpret/builtins/hosts/base/BaseAnalyzer.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/variable/FunctionWrapper.h"
#include "simodo/inout/convert/functions.h"

#include <cassert>

inline const std::string EXCEEDED = "The number of errors has exceeded the allowable limit";

namespace 
{
    simodo::interpret::SemanticDataCollector_null null_collector;
}

namespace simodo::interpret::builtins
{

BaseAnalyzer::BaseAnalyzer(Interpret * inter)
    : BaseInterpret_abstract(inter)
    , _collector(null_collector)
{
}

BaseAnalyzer::BaseAnalyzer(Interpret * inter, SemanticDataCollector_interface & collector)
    : BaseInterpret_abstract(inter)
    , _collector(collector)
{
}

void BaseAnalyzer::importNamespace(std::u16string name, variable::Object ns, inout::TokenLocation location) 
{
    BaseInterpret_abstract::importNamespace(name, ns, location);

    // machine().stack().top().origin().spec().set(u"module", true);

    _collector.collectNameDeclared(machine().stack().top());
}

InterpretState BaseAnalyzer::before_start()
{
    return BaseInterpret_abstract::before_start();
}

InterpretState BaseAnalyzer::before_finish(InterpretState state)
{
    return BaseInterpret_abstract::before_finish(state);
}

InterpretState BaseAnalyzer::performOperation(const ast::Node & op)
{
    inout::Location location {inout::null_location};

    try
    {
        return BaseInterpret_abstract::performOperation(op);
    }
    catch(const AnalyzeException & e)
    {
        location = e.location();
        inter().reporter().reportError(location, e.what());
        inter().setErrorSign();
        _number_of_mistakes ++;
    }

    if (_number_of_mistakes >= MAX_NUMBER_OF_MISTAKES) 
        throw AnalyzeException("ScriptAnalyzer::performOperation", 
                                op.token().makeLocation(inter().files()), 
                                EXCEEDED);
    
    return InterpretState::Flow;
}

InterpretState BaseAnalyzer::executePushValue(const inout::Token & variable_name)
{
    /// \note Перехватывать исключение AnalyzeException не нужно, т.к. оно обрабатывается в BaseAnalyzer::performOperation
    InterpretState state = BaseInterpret_abstract::executePushValue(variable_name);

    const variable::Variable & ref = machine().stack().top();

    if (ref.type() != variable::ValueType::Ref)
        return state; // Что-то пошло не так...

    const variable::Variable & var = ref.origin();

    _collector.collectNameUsed(var, variable_name.location());

    return state;
}

InterpretState BaseAnalyzer::executeObjectElement(const inout::Token & dot, const inout::Token & variable_name)
{
    /// \note Перехватывать исключение AnalyzeException не нужно, т.к. оно обрабатывается в BaseAnalyzer::performOperation
    InterpretState state = BaseInterpret_abstract::executeObjectElement(dot, variable_name);

    const variable::Variable & ref = machine().stack().top();

    if (ref.type() != variable::ValueType::Ref)
        return state; // Что-то пошло не так...

    const variable::Variable & var = ref.origin();

    _collector.collectNameUsed(var, variable_name.location());

    return state;
}

InterpretState BaseAnalyzer::executeFunctionCall(const ast::Node & op)
{
    size_t args_stack_position = machine().stack().size();

    inter().addFlowLayer(
                op, 
                [this, args_stack_position](FlowLayerInfo flow) {
                    variable::Value v;

                    if (!flow.error_sign) {
                        try {
                            const variable::Variable &   function_variable = machine().stack().at(args_stack_position-1);
                            variable::FunctionWrapper    wrapper(function_variable);
                            variable::VariableSetWrapper params = wrapper.getArgumentDeclarationVariables();
                            if (machine().stack().size() - args_stack_position != params.size())
                                throw AnalyzeException(
                                    "BaseAnalyzer::executeFunctionCall", 
                                    function_variable.location().makeLocation(inter().files()),
                                    "The number of given parameters does not match the number of declared arguments for function '" 
                                    + inout::toU8(function_variable.name()) + "'");
                            for(size_t i=0; i < params.size(); ++i) {
                                if (machine().stack().at(args_stack_position+i).origin().type() != params[i].type()
                                 && params[i].type() != variable::ValueType::Null)
                                    throw AnalyzeException(
                                        "BaseAnalyzer::executeFunctionCall", 
                                        machine().stack().at(args_stack_position+i).location().makeLocation(inter().files()),
                                        "Wrong parameter type");
                            }
                        }
                        catch(...) {
                            // Перед выходом восстанавливаем ожидаемое состояние выполнения команды
                            machine().popAmount(machine().stack().size() - args_stack_position);
                            machine().pop();
                            machine().push(variable::null_variable()); 
                            throw;
                        }
                    }
                    machine().popAmount(machine().stack().size() - args_stack_position);
                    machine().pop();
                    machine().push(variable::null_variable()); 
                });
    
    return InterpretState::Flow;
}

InterpretState BaseAnalyzer::executeBlock(const ast::Node & op)
{
    if (op.token().lexeme() == u"{") {
        _open_scope.push_back(op.token().location());
    }
    else {
        assert(!_open_scope.empty());
        _collector.collectScoped(_open_scope.back(), op.token().location());
        _open_scope.pop_back();
    }

    return BaseInterpret_abstract::executeBlock(op);
}

}